package com.landon.id.generator.segment;

import com.landon.id.generator.IdGeneratorApi;
import com.landon.id.generator.model.SegmentIdDO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author shishaopeng
 * Date: 2022-10-24-21-10
 * Desc: 存储号段
 */
@Slf4j
@Component
public class BusinessCosIdSegmentCache {

    /**
     * 业务号段map
     * <String, BusinessCosIdSegmentChain>
     * <业务标识, 业务下的号段链对象>
     * BusinessCosIdSegmentChain继承了LinkedList，所以它是一个链表，所有的号段都存储在里面，新的号段会追加在链表尾部
     * eg：BusinessCosIdGenerator1 -> BusinessCosIdGenerator2 -> BusinessCosIdGenerator3(最新的号段)
     * 已经使用过的号段会被移出链表
     * eg：BusinessCosIdGenerator2 -> BusinessCosIdGenerator3(最新的号段)
     */
    private final Map<String, BusinessCosIdSegmentChain> idGeneratorAutoIncrementMap = new ConcurrentHashMap<>();

    /**
     * 默认安全距离 小于这个距离即不再追加号段
     */
    private static final long DEFAULT_SAFE_DISTANCE = 5L;

    /**
     * 默认预取号段的数量
     */
    private static final long DEFAULT_PREFETCHING_COUNT = 100;

    /**
     * 饥饿阈值（单位秒）号段的使用时间低于这个阈值代表消耗速度很快，需要快速膨胀（这是一个压测出来的值））
     */
    private static final long HUNGER_THRESHOLD = 30;

    /**
     * 最大预取号段距离
     */
    private static final long MAX_PREFETCH_DISTANCE = Long.MAX_VALUE;

    @Resource
    private IdGeneratorApi idGeneratorApi;

    /**
     * 初始化，并获取号段链
     *
     * @param businessId 业务标识
     * @return 号段链
     */
    public BusinessCosIdSegmentChain getBusinessCosIdGenerator(String businessId) {
        BusinessCosIdSegmentChain businessCosIdGenerators = idGeneratorAutoIncrementMap.get(businessId);
        if (businessCosIdGenerators == null) {
            // 不存在就构造一个新的业务号段链
            businessCosIdGenerators = new BusinessCosIdSegmentChain(businessId, DEFAULT_SAFE_DISTANCE, DEFAULT_PREFETCHING_COUNT, this::generatorSegmentChain);
            idGeneratorAutoIncrementMap.put(businessId, businessCosIdGenerators);
        }
        return businessCosIdGenerators;
    }

    /**
     * 生成号段链
     */
    public BusinessCosIdSegmentChain.BusinessCosIdGenerator generatorSegmentChain(BusinessCosIdSegmentChain businessCosIdSegmentChain) {
        String businessId = businessCosIdSegmentChain.getBusinessId();
        /**
         * 计算出新号段的预取数
         */
        // 上次预取时间（往里面塞号段的时间）
        LocalDateTime putSegmentChainTime = businessCosIdSegmentChain.getPutSegmentChainTime();
        // 饥饿时间 （上次进行新增号段的时间）
        LocalDateTime hungerTime = businessCosIdSegmentChain.getHungerTime();
        // 当前号段已经使用了多长时间
        long wakeupTimeGap = putSegmentChainTime == null ? HUNGER_THRESHOLD : Duration.between(putSegmentChainTime, hungerTime).toMillis() / 1000;
        // 本次预取数
        long nowPrefetchingCount;
        // 上次预取数
        Long lastPrefetchingCount = businessCosIdSegmentChain.getLastPrefetchingCount();
        // 如果当前号段的使用时间要比这个饥饿阈值小 那代表号段使用需求程度是非常激烈的 需要快速扩容
        if (wakeupTimeGap < HUNGER_THRESHOLD) {
            log.info("饥饿了");
            long max1 = Math.max(wakeupTimeGap, 1);
            long div = Math.floorDiv(HUNGER_THRESHOLD, max1);
            long max2 = Math.max(div, 2);
            long multiply = Math.multiplyExact(lastPrefetchingCount, max2);
            nowPrefetchingCount = Math.min(multiply, MAX_PREFETCH_DISTANCE);
            // 上诉算法的含意：假设现在饥饿阈值=30s，当前号段消耗了15s，那么下一次预取的数量 = （30 - 15）* 上次预取的数量，最低预取数=原来 2 倍
            // 所以在饥饿情况下下一次预取的数量= （2倍原来预取数，算法算出来的新数]
        } else {
            log.info("不饿");
            // 当前号段的消耗时间是要>30s意味着不饿，那在下一轮号段内id数量直接砍半或者用最小安全距离长度
            nowPrefetchingCount = Math.max(Math.floorDiv(lastPrefetchingCount, 2), businessCosIdSegmentChain.getSafeDistanceLength());
        }
        businessCosIdSegmentChain.setLastPrefetchingCount(nowPrefetchingCount);
        log.info("本次预取号段 {}", nowPrefetchingCount);

        // 计算新的安全距离
        long newSafeDistance = Math.floorDiv(nowPrefetchingCount, 2);
        businessCosIdSegmentChain.setSafeDistanceLength(Math.max(newSafeDistance, DEFAULT_SAFE_DISTANCE));

        // 将最新的预取数量更新到数据库
        SegmentIdDO segmentIdDO = idGeneratorApi.generateSegmentId(businessId, nowPrefetchingCount);

        // 返回新生成的号段
        return new BusinessCosIdSegmentChain.BusinessCosIdGenerator(segmentIdDO.getBusinessId(), segmentIdDO.getStartId(), segmentIdDO.getEndId());
    }
}
